package fi.libros.gae;

import java.io.Serializable;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.CannotCreateTransactionException;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.transaction.UnexpectedRollbackException;
import org.springframework.transaction.support.DefaultTransactionStatus;

import com.google.appengine.api.datastore.DatastoreConfig;
import com.google.appengine.api.datastore.DatastoreFailureException;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Transaction;


/**
 * {@link org.springframework.transaction.PlatformTransactionManager} implementation
 * for Google App Engine Datastore.
 * 
 * @author Marko Asplund
 */
public class SimpleDatastoreTransactionManager implements PlatformTransactionManager, Serializable {
	private static final long serialVersionUID = 1L;
	final Logger logger = LoggerFactory.getLogger(SimpleDatastoreTransactionManager.class);
	private DatastoreConfig datastoreConfig;

	
	public SimpleDatastoreTransactionManager() {
		logger.debug("creating new "+SimpleDatastoreTransactionManager.class.getName());
		datastoreConfig = DatastoreConfig.DEFAULT;
	}

	@Override
	public TransactionStatus getTransaction(TransactionDefinition def) throws TransactionException {
		if(TransactionDefinition.PROPAGATION_REQUIRED != def.getPropagationBehavior())
			throw new PropagationModeNotSupportedException("Only REQUIRED propagation mode supported");
		DatastoreService ds = DatastoreServiceFactory.getDatastoreService(datastoreConfig);
		Transaction tx = ds.getCurrentTransaction(null);
		if(tx == null) {
			logger.debug("creating new transaction");
			try {
				tx = ds.beginTransaction();
			} catch (DatastoreFailureException ex) {
				throw new CannotCreateTransactionException("Datastore failed to create transaction", ex);
			}
		} else {
			logger.debug("using existing transaction");
		}
		DefaultTransactionStatus status = new DefaultTransactionStatus(tx, true, false, false, false, null);
		logger.debug("getTransaction: "+tx.getId());
		return status;
	}

	@Override
	public void commit(TransactionStatus status) throws TransactionException {
		DefaultTransactionStatus st = (DefaultTransactionStatus)status;
		Transaction tx = (Transaction)st.getTransaction();
		if(status.isRollbackOnly()) {
			try {
				tx.rollback();
			} catch (DatastoreFailureException ex) {
				throw new UnexpectedRollbackException("Datastore failed to roll back", ex);
			}
		}
		logger.debug("commit: "+tx.getId());
		try {
			tx.commit();
		} catch (DatastoreFailureException ex) {
			throw new TransactionSystemException("Datastore failed to commit", ex);
		}
	}
	
	@Override
	public void rollback(TransactionStatus status) throws TransactionException {
		DefaultTransactionStatus st = (DefaultTransactionStatus)status;
		Transaction tx = (Transaction)st.getTransaction();
		logger.debug("rolling back tx: "+tx.getId());
		try {
			tx.rollback();
		} catch (DatastoreFailureException ex) {
			throw new TransactionSystemException("Datastore failed to roll back", ex);
		}
	}

	public DatastoreConfig getDatastoreConfig() {
		return datastoreConfig;
	}

	public void setDatastoreConfig(DatastoreConfig datastoreConfig) {
		this.datastoreConfig = datastoreConfig;
	}

}
